package core.XML;

import interfaces.GeneratorInterface;

import java.io.File;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Vector;

import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import core.environment.Defines;
import core.generators.CSVGenerator;
import core.generators.DoubleSetGenerator;
import core.generators.DummyGen;
import core.generators.FunctionGenerator;
import core.generators.IntRangeGenerator;
import core.generators.IntSetGenerator;
import core.generators.RandomDoubleGenerator;
import core.generators.RandomDoubleSetGenerator;
import core.generators.RandomIntRangeGenerator;
import core.generators.RandomIntSetGenerator;
import core.generators.RandomStringSetGenerator;
import core.generators.StepDoubleGenerator;
import core.generators.StepIntGenerator;
import core.generators.StringSetGenerator;
import core.generators.nested.ConcatGenerator;
import core.generators.nested.CropGenerator;
import core.generators.nested.JoinGenerator;
import core.generators.nested.ShuffleGenerator;
import core.generators.nested.SplitGenerator;
import core.generators.nested.StackGenerator;

/**
 * Static factory of generators from XML description
 * 
 * @author Sergey and Julia
 * 
 */
@SuppressWarnings("nls")
public class GeneratorsParser {

	private static final String defaultDelimiter = ",";

	static HashMap<String, Method> littleParsers;
	static {
		littleParsers = new HashMap<String, Method>();

		try {
			Class<?>[] args = { NamedNodeMap.class, NodeList.class,
					Integer.class };
			littleParsers.put(DefinesXML.GEN_INTSET, GeneratorsParser.class
					.getMethod("intSet", args));
			littleParsers.put(DefinesXML.GEN_DOUBLESET, GeneratorsParser.class
					.getMethod("doubleSet", args));
			littleParsers.put(DefinesXML.GEN_STRINGSET, GeneratorsParser.class
					.getMethod("stringSet", args));
			littleParsers.put(DefinesXML.GEN_INTRANGE, GeneratorsParser.class
					.getMethod("intRange", args));

			littleParsers.put(DefinesXML.GEN_RNDINTSET, GeneratorsParser.class
					.getMethod("rndIntSet", args));
			littleParsers.put(DefinesXML.GEN_RNDSTRINGSET,
					GeneratorsParser.class.getMethod("rndStringSet", args));

			littleParsers.put(DefinesXML.GEN_RNDDOUBLESET,
					GeneratorsParser.class.getMethod("rndDoubleSet", args));

			littleParsers.put(DefinesXML.GEN_RNDDOUBLE, GeneratorsParser.class
					.getMethod("rndDouble", args));

			littleParsers.put(DefinesXML.GEN_RNDINTRANGE,
					GeneratorsParser.class.getMethod("rndIntRange", args));

			littleParsers.put(DefinesXML.GEN_STEPDOUBLE, GeneratorsParser.class
					.getMethod("stepDouble", args));

			littleParsers.put(DefinesXML.GEN_STEPINT, GeneratorsParser.class
					.getMethod("stepInt", args));

			littleParsers.put(DefinesXML.GEN_FUNCTION, GeneratorsParser.class
					.getMethod("function", args));

			littleParsers.put(DefinesXML.GEN_CSV, GeneratorsParser.class
					.getMethod("csv", args));
			littleParsers.put(DefinesXML.GEN_CONCAT, GeneratorsParser.class
					.getMethod("concat", args));
			littleParsers.put(DefinesXML.GEN_STACK, GeneratorsParser.class
					.getMethod("stack", args));
			littleParsers.put(DefinesXML.GEN_CROP, GeneratorsParser.class
					.getMethod("crop", args));
			littleParsers.put(DefinesXML.GEN_JOIN, GeneratorsParser.class
					.getMethod("join", args));
			littleParsers.put(DefinesXML.GEN_SPLIT, GeneratorsParser.class
					.getMethod("split", args));
			littleParsers.put(DefinesXML.GEN_LOOP, GeneratorsParser.class
					.getMethod("loop", args)); 
			littleParsers.put(DefinesXML.GEN_FOREACH, GeneratorsParser.class
						.getMethod("foreach", args)); 
			littleParsers.put(DefinesXML.GEN_SHUFFLE, GeneratorsParser.class
					.getMethod("shuffle", args)); 

		} catch (SecurityException e) {
			Defines.printException(e.getMessage());
		} catch (NoSuchMethodException e) {
			Defines.printException(e.getMessage());
		}
	}

	/**
	 * Get a node of xml tree describing generator and construct it
	 * 
	 * @param generatorNode
	 * @param value
	 * @return Constructed and inited generator
	 */
	public static GeneratorInterface<?>[] parseGenerator(Node generatorNode,
			Integer value) {
		assert (generatorNode.getLocalName().equals(DefinesXML.GEN_TAG));

		NamedNodeMap atts = generatorNode.getAttributes();
		Node type = atts.getNamedItem(DefinesXML.XML_TYPE);

		if (type != null)
			return parseAttributes(type.getNodeValue(), atts, generatorNode
					.getChildNodes(), value);
		// else
		Defines.printException(DefinesXML.BADXML_ERROR_STRING);
		return null;

	}

	@SuppressWarnings("nls")
	//should be renamed... 
	private static GeneratorInterface<?>[] parseAttributes(String type,
			NamedNodeMap atts, NodeList nl, Integer value) {
		assert (type != null && atts != null);
		try {
			Method m = littleParsers.get(type);
			Object result = m.invoke(null, atts, nl, value);
			return (GeneratorInterface<?>[]) result;
		} catch (IllegalArgumentException e) {
			Defines.printException(e.getMessage()
					+ " [Generator parseAttributes]");
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			Defines.printException(e.getMessage()
					+ " [Generator parseAttributes]");
			e.printStackTrace();
		} catch (InvocationTargetException e) {
			Defines.printException(e.getMessage()
					+ " [Generator parseAttributes]");
			e.printStackTrace();
		}
		return null;

	}

	/**
	 * Parse int_set generator
	 * @param atts attributes of this element
	 * @param nl  nested XML elements
	 * @param value a value sent from above. can be null
	 * @return constructed generator
	 */
	public static GeneratorInterface<?>[] intSet(NamedNodeMap atts,
			NodeList nl, Integer value) {
		String set = atts.getNamedItem(DefinesXML.XML_SET).getNodeValue();
		if (set != null)
			return wrapInArray(new IntSetGenerator(set, defaultDelimiter));

		Defines.printException(DefinesXML.BADXML_ERROR_STRING);
		return null;
	}

	/**
	 * Parse double_set generator
	 * @param atts attributes of this element
	 * @param nl  nested XML elements
	 * @param value a value sent from above. can be null
	 * @return constructed generator
	 */
	public static GeneratorInterface<?>[] doubleSet(NamedNodeMap atts,
			NodeList nl, Integer value) {
		String set = atts.getNamedItem(DefinesXML.XML_SET).getNodeValue();
		if (set != null)
			return wrapInArray(new DoubleSetGenerator(set, defaultDelimiter));

		Defines.printException(DefinesXML.BADXML_ERROR_STRING);
		return null;
	}

	/**
	 * Parse string_set generator
	 * @param atts attributes of this element
	 * @param nl  nested XML elements
	 * @param value a value sent from above. can be null
	 * @return constructed generator
	 */
	public static GeneratorInterface<?>[] stringSet(NamedNodeMap atts,
			NodeList nl, Integer value) {
		String set = atts.getNamedItem(DefinesXML.XML_SET).getNodeValue();
		if (set != null)
			return wrapInArray(new StringSetGenerator(set, defaultDelimiter));

		Defines.printException(DefinesXML.BADXML_ERROR_STRING);
		return null;
	}

	/**
	 * Parse rnd_set generator
	 * @param atts attributes of this element
	 * @param nl  nested XML elements
	 * @param value a value sent from above. can be null
	 * @return constructed generator
	 */
	@SuppressWarnings("boxing")
	public static GeneratorInterface<?>[] rndIntSet(NamedNodeMap atts,
			NodeList nl, Integer value) {
		String set = atts.getNamedItem(DefinesXML.XML_SET).getNodeValue();
		int seed = getIntFromAtt(atts.getNamedItem(DefinesXML.GEN_SEED), value);
		if (set != null)
			return wrapInArray(new RandomIntSetGenerator(set, defaultDelimiter, seed));

		Defines.printException(DefinesXML.BADXML_ERROR_STRING);
		return null;
	}

	/**
	 * Parse rnd_double_set generator
	 * @param atts attributes of this element
	 * @param nl  nested XML elements
	 * @param value a value sent from above. can be null
	 * @return constructed generator
	 */
	@SuppressWarnings("boxing")
	public static GeneratorInterface<?>[] rndDoubleSet(NamedNodeMap atts,
			NodeList nl, Integer value) {
		String set = atts.getNamedItem(DefinesXML.XML_SET).getNodeValue();
		
		int seed = getIntFromAtt(atts.getNamedItem(DefinesXML.GEN_SEED), value);
		if (set != null)
			return wrapInArray(new RandomDoubleSetGenerator(set,
					defaultDelimiter, seed));

		Defines.printException(DefinesXML.BADXML_ERROR_STRING);

		return null;
	}

	/**
	 * Parse rnd_string_set generator
	 * @param atts attributes of this element
	 * @param nl  nested XML elements
	 * @param value a value sent from above. can be null
	 * @return constructed generator
	 */
	@SuppressWarnings("boxing")
	public static GeneratorInterface<?>[] rndStringSet(NamedNodeMap atts,
			NodeList nl, Integer value) {
		String set = atts.getNamedItem(DefinesXML.XML_SET).getNodeValue();
		int seed = getIntFromAtt(atts.getNamedItem(DefinesXML.GEN_SEED), value);
		if (set != null)
			return wrapInArray(new RandomStringSetGenerator(set,
					defaultDelimiter, seed));

		Defines.printException(DefinesXML.BADXML_ERROR_STRING);
		return null;
	}

	/**
	 * Parse int_range generator
	 * @param atts attributes of this element
	 * @param nl  nested XML elements
	 * @param value a value sent from above. can be null
	 * @return constructed generator
	 */
	@SuppressWarnings("boxing")
	public static GeneratorInterface<?>[] intRange(NamedNodeMap atts,
			NodeList nl, Integer value) {
		int left = getIntFromAtt(atts.getNamedItem(DefinesXML.GEN_LEFT), value);
		int right = getIntFromAtt(atts.getNamedItem(DefinesXML.GEN_RIGHT),
				value);
		return wrapInArray(new IntRangeGenerator(left, right));
	}

	/**
	 * Parse rnd_int_range generator
	 * @param atts attributes of this element
	 * @param nl  nested XML elements
	 * @param value a value sent from above. can be null
	 * @return constructed generator
	 */
	@SuppressWarnings("boxing")
	public static GeneratorInterface<?>[] rndIntRange(NamedNodeMap atts,
			NodeList nl, Integer value) {
		int left = getIntFromAtt(atts.getNamedItem(DefinesXML.GEN_LEFT), value);
		int right = getIntFromAtt(atts.getNamedItem(DefinesXML.GEN_RIGHT),
				value);
		int count = getIntFromAtt(atts.getNamedItem(DefinesXML.GEN_COUNT),
				value);
		long seed = Long.parseLong(atts.getNamedItem(DefinesXML.GEN_SEED)
				.getNodeValue());
		return wrapInArray(new RandomIntRangeGenerator(left, right, count, seed));
	}

	/**
	 * Parse rnd_double generator
	 * @param atts attributes of this element
	 * @param nl  nested XML elements
	 * @param value a value sent from above. can be null
	 * @return constructed generator
	 */
	@SuppressWarnings("boxing")
	public static GeneratorInterface<?>[] rndDouble(NamedNodeMap atts,
			NodeList nl, Integer value) {
		long seed = getIntFromAtt(atts.getNamedItem(DefinesXML.GEN_SEED),value);
		int count = getIntFromAtt(atts.getNamedItem(DefinesXML.GEN_COUNT),
				value);
		RandomDoubleGenerator gen = new RandomDoubleGenerator(seed);
		gen.init(count);
		return wrapInArray(gen);
	}

	/**
	 * Parse step_double generator
	 * @param atts attributes of this element
	 * @param nl  nested XML elements
	 * @param value a value sent from above. can be null
	 * @return constructed generator
	 */
	@SuppressWarnings("boxing")
	public static GeneratorInterface<?>[] stepDouble(NamedNodeMap atts,
			NodeList nl, Integer value) {
		Double left = Double.parseDouble(atts.getNamedItem(DefinesXML.GEN_LEFT)
				.getNodeValue());
		Double step = Double.parseDouble(atts.getNamedItem(DefinesXML.GEN_STEP)
				.getNodeValue());
		int count = getIntFromAtt(atts.getNamedItem(DefinesXML.GEN_COUNT),
				value);
		StepDoubleGenerator gen = new StepDoubleGenerator(left, step);
		gen.init(count);
		return wrapInArray(gen);
	}

	/**
	 * Parse stepInt generator
	 * @param atts attributes of this element
	 * @param nl  nested XML elements
	 * @param value a value sent from above. can be null
	 * @return constructed generator
	 */
	@SuppressWarnings("boxing")
	public static GeneratorInterface<?>[] stepInt(NamedNodeMap atts,
			NodeList nl, Integer value) {
		Integer left = getIntFromAtt(atts.getNamedItem(DefinesXML.GEN_LEFT),
				value);
		Integer step = getIntFromAtt(atts.getNamedItem(DefinesXML.GEN_STEP),
				value);
		int count = getIntFromAtt(atts.getNamedItem(DefinesXML.GEN_COUNT),
				value);
		return wrapInArray(new StepIntGenerator(left, step).init(count));
	}

	/**
	 * Parse function generator
	 * @param atts attributes of this element
	 * @param nl  nested XML elements
	 * @param value a value sent from above. can be null
	 * @return constructed generator
	 */
	@SuppressWarnings( { "boxing", "null" })
	public static GeneratorInterface<?>[] function(NamedNodeMap atts,
			NodeList nl, Integer value) {
		Integer left = Integer.parseInt(atts.getNamedItem(DefinesXML.GEN_LEFT)
				.getNodeValue());
		String className = atts.getNamedItem(DefinesXML.GEN_CLASS)
				.getNodeValue();
		int count = getIntFromAtt(atts.getNamedItem(DefinesXML.GEN_COUNT),
				value);
		FunctionGenerator<?> fgen = null;
		try {
			Class<?> c = Class.forName(className);
			Constructor<?> constr = c.getConstructor(Object.class);
			fgen = (FunctionGenerator<?>) constr.newInstance(left);
		} catch (ClassNotFoundException e) {
			Defines.printException(DefinesXML.BADXML_ERROR_STRING
					+ e.getMessage());
		} catch (SecurityException e) {
			Defines.printException(e.getMessage());
		} catch (NoSuchMethodException e) {
			Defines.printException(e.getMessage());
		} catch (IllegalArgumentException e) {
			Defines.printException(e.getMessage());
		} catch (InstantiationException e) {
			Defines.printException(e.getMessage());
		} catch (IllegalAccessException e) {
			Defines.printException(e.getMessage());
		} catch (InvocationTargetException e) {
			Defines.printException(e.getMessage());
		}

		fgen.generate(count);
		return wrapInArray(fgen);
	}

	/**
	 * Parse crop generator
	 * @param atts attributes of this element
	 * @param nl  nested XML elements
	 * @param value a value sent from above. can be null
	 * @return constructed generator
	 */
	@SuppressWarnings("boxing")
	public static GeneratorInterface<?>[] crop(NamedNodeMap atts, NodeList nl,
			Integer value) {

		int top;
		int left;
		int height;
		int width;
		Node att;

		att = atts.getNamedItem(DefinesXML.GEN_TOP);
		top = getIntFromAtt(att, value);
		att = atts.getNamedItem(DefinesXML.GEN_LEFT);
		left = getIntFromAtt(att, value);
		att = atts.getNamedItem(DefinesXML.GEN_HEIGHT);
		height = getIntFromAtt(att, value);
		att = atts.getNamedItem(DefinesXML.GEN_WIDTH);
		width = getIntFromAtt(att, value);

		return wrapInArray(new CropGenerator(getNested(nl,value).elementAt(0), left,
				top, width, height));

	}

	/**
	 * Parse csv generator
	 * @param atts attributes of this element
	 * @param nl  nested XML elements
	 * @param value a value sent from above. can be null
	 * @return constructed generator
	 */
	public static GeneratorInterface<?>[] csv(NamedNodeMap atts, NodeList nl,
			Integer value) {
		Node att;
		att = atts.getNamedItem(DefinesXML.XML_NAME);
		return wrapInArray(new CSVGenerator(new File(att.getNodeValue())));
	}

	/**
	 * Parse join generator
	 * @param atts attributes of this element
	 * @param nl  nested XML elements
	 * @param value a value sent from above. can be null
	 * @return constructed generator
	 */
	public static GeneratorInterface<?>[] join(NamedNodeMap atts, NodeList nl,
			Integer value) {
		return wrapInArray(new JoinGenerator(getNested(nl,value)));
	}

	/**
	 * Parse concat generator
	 * @param atts attributes of this element
	 * @param nl  nested XML elements
	 * @param value a value sent from above. can be null
	 * @return constructed generator
	 */
	public static GeneratorInterface<?>[] concat(NamedNodeMap atts,
			NodeList nl, Integer value) {
		return wrapInArray(new ConcatGenerator(getNested(nl,value)));
	}
	
	/**
	 * Parse shuffle generator
	 * @param atts attributes of this element
	 * @param nl  nested XML elements
	 * @param value a value sent from above. can be null
	 * @return constructed generator
	 */
	@SuppressWarnings("boxing")
	public static GeneratorInterface<?>[] shuffle(NamedNodeMap atts,
			NodeList nl, Integer value) {
		long seed = getIntFromAtt(atts.getNamedItem(DefinesXML.GEN_SEED),value);
		return wrapInArray(new ShuffleGenerator(getNested(nl,value),seed));
	}
	
	/**
	 * Parse stack generator
	 * @param atts attributes of this element
	 * @param nl  nested XML elements
	 * @param value a value sent from above. can be null
	 * @return constructed generator
	 */
	public static GeneratorInterface<?>[] stack(NamedNodeMap atts,
			NodeList nl, Integer value) {
		return wrapInArray(new StackGenerator(getNested(nl,value)));
	}

	/**
	 * Parse split generator
	 * @param atts attributes of this element
	 * @param nl  nested XML elements
	 * @param value a value sent from above. can be null
	 * @return constructed generator
	 */
	@SuppressWarnings("boxing")
	public static GeneratorInterface<?>[] split(NamedNodeMap atts, NodeList nl,
			Integer value) {

		int left;
		int length;
		Node att;

		att = atts.getNamedItem(DefinesXML.GEN_COUNT);
		length = getIntFromAtt(att, value);
		att = atts.getNamedItem(DefinesXML.GEN_LEFT);
		left = getIntFromAtt(att, value);

		return wrapInArray(new SplitGenerator(getNested(nl,value).elementAt(0), left,
				length));
	}

	/**
	 * Parse loop generator
	 * @param atts attributes of this element
	 * @param nl  nested XML elements
	 * @param value a value sent from above. can be null
	 * @return constructed generator
	 */
	@SuppressWarnings("boxing")
	public static GeneratorInterface<?>[] loop(NamedNodeMap atts, NodeList nl,
			Integer value) {
		int count;
		Node att;

		att = atts.getNamedItem(DefinesXML.GEN_COUNT);
		count = getIntFromAtt(att, value);

		Vector<GeneratorInterface<?>> nested = new Vector<GeneratorInterface<?>>();
		for (int var = 0; var < count; ++var)
			for (int i = 0; i < nl.getLength(); ++i) {
				Node item = nl.item(i);
				if (DefinesXML.GEN_TAG.equals(item.getNodeName())) {
					for (GeneratorInterface<?> genInt : GeneratorsParser
							.parseGenerator(item, var))
						nested.add(genInt);
				}
			}
		return wrapInArray(nested.toArray());

	}
	
	/**
	 * Parse foreach generator
	 * @param atts attributes of this element
	 * @param nl  nested XML elements
	 * @param value a value sent from above. can be null
	 * @return constructed generator
	 */
	public static GeneratorInterface<?>[] foreach(NamedNodeMap atts, NodeList nl,
			Integer value) {

		Vector<GeneratorInterface<?>> nested = new Vector<GeneratorInterface<?>>();
		//get the first generator:
		GeneratorInterface<?> values = new DummyGen();
		int i;
		for ( i=0; i < nl.getLength(); ++i) {
			Node item = nl.item(i);
			if (DefinesXML.GEN_TAG.equals(item.getNodeName())) {
				values = GeneratorsParser.parseGenerator(item, value)[0];
				break;
			}  
		}
		//now use the values to feed the nested gens

		for (Object var : values.getVector()){
			for (int j = i+1/* continue from the next nested */ ; j < nl.getLength(); ++j) {
				Node item = nl.item(j);
				if (DefinesXML.GEN_TAG.equals(item.getNodeName())) {
					for (GeneratorInterface<?> genInt : GeneratorsParser
							.parseGenerator(item, (Integer) var)) //assume generator of integers
						nested.add(genInt);
				}
			}
		}
		return wrapInArray(nested.toArray());

	}	

	@SuppressWarnings("boxing")
	private static Vector<GeneratorInterface<?>> getNested(NodeList nl, Integer value) {
		Vector<GeneratorInterface<?>> nested = new Vector<GeneratorInterface<?>>();

		for (int i = 0; i < nl.getLength(); ++i)
			if (DefinesXML.GEN_TAG.equals(nl.item(i).getNodeName())) {

				GeneratorInterface<?>[] gen = GeneratorsParser.parseGenerator(
						nl.item(i), value);

				for (GeneratorInterface<?> genInt : gen)
					nested.add(genInt);

			}
		return nested;
	}

	@SuppressWarnings("boxing")
	private static Integer getIntFromAtt(Node att, Integer val) {
		String str = att.getNodeValue();
		if (str.equals(DefinesXML.GEN_VAR))
			return val;
		return Integer.parseInt(str);
	}

	private static GeneratorInterface<?>[] wrapInArray(
			GeneratorInterface<?> single) {
		GeneratorInterface<?> tmp[] = { single };
		return tmp;
	}

	private static GeneratorInterface<?>[] wrapInArray(Object[] many) {
		GeneratorInterface<?>[] tmp = new GeneratorInterface<?>[many.length];
		for (int i = 0; i < many.length; ++i)
			tmp[i] = (GeneratorInterface<?>) many[i];
		return tmp;
	}
}
